<?php
use Workerman\Mysql;
use Workerman\Worker;
use Workerman\Lib\Timer;
use Workerman\Events\EventInterface;
use Workerman\Redis\Client;

//字节码转16进制字符串
function bytetostr($str)
{
    $arr = str_split($str, 1);
    $strs = [];
    foreach ($arr as $h) {
        $s = dechex(ord($h));
        $s = is_numeric($s)?($s<10?'0'.$s:$s):$s;
        $strs[] = $s;
    }
    return strtoupper(implode("", $strs));
}

//16进制字符串转字节码
function tobyte($str)
{
    $arr = str_split(str_ireplace(' ', '', $str), 2);//去掉空格后，2 字符一段切分成数组
    $byte_arr = array();
    foreach ($arr as $h) {
        $byte_arr[] = chr(hexdec($h));
    }

    $byte_str = implode('', $byte_arr);
    return $byte_str;
}

//16进制字符串转字符串
function hexToStr($hex){
    $str="";
    for($i=0;$i<strlen($hex)-1;$i+=2)
        $str.=chr(hexdec($hex[$i].$hex[$i+1]));
    return $str;
}

//16进制字符串异或加密
function getXOR($str)
{
    $crytxt = '';
    $str_arr = str_split($str, 2);
    $m = 0;
    for($i=0; $i<strlen($str); $i++) {
        $m++;
        if(isset($str_arr[$m])){
            $a = $crytxt?$crytxt:$str_arr[$i];
            $crytxt = chr(hexdec(($a))) ^ chr(hexdec(($str_arr[$m])));
            $crytxt = bytetostr($crytxt);
        }
    }
    return $crytxt;
}

require_once __DIR__ . '/workerman/Autoloader.php';
require_once __DIR__ . '/workerman/MySql/Connection.php';
require_once __DIR__ . '/workerman/Redis/Client.php';

// 将屏幕打印输出到Worker::$stdoutFile指定的文件中
Worker::$stdoutFile = '/tmp/stdout.log';

// 心跳间隔30秒
define('HEARTBEAT_TIME', 30);

// 创建一个Worker监听2345端口，使用http协议通讯
$http_worker = new Worker("tcp://0.0.0.0:8082");

$http_worker->uidConnections = array();

// 启动4个进程对外提供服务
$http_worker->count = 1;

$http_worker->onConnect = function($connection)
{
    if($connection->getRemoteIp()!='127.0.0.1'){
        echo "new connection from ip " . $connection->getRemoteIp() .','. date("Y-m-d H:i:s"). "\n";
    }


    //发送获取设备号指令
    $connection->send(tobyte('4E42534508008193'));
};

$http_worker->onWorkerStart = function($worker)
{
    // 当进程收到SIGALRM信号时，打印输出一些信息
    Worker::$globalEvent->add(SIGALRM, EventInterface::EV_SIGNAL, function()
    {
        $folderPath = __DIR__.'/log/'.date("Ym");
        is_dir($folderPath) || @mkdir($folderPath) || die("Can't Create folder");

        $file_directory = $folderPath.'/'.date("d").".log";
        file_put_contents($file_directory, iconv("gbk", 'utf8','Pid is ' . posix_getpid())."\n",FILE_APPEND);
    });

    global $db;
    $db = new \Workerman\MySQL\Connection('127.0.0.1', '3306', 'jcg', 'y2pak8kmcNYJfrhM', 'jcg');

    global $redis;
    $redis = new Client('redis://127.0.0.1:6379');

    // 定时，每30秒一次
    Timer::add(30, function()use($worker)
    {
        $time_now = time();
        foreach($worker->connections as $connection) {

            // 有可能该connection还没收到过消息，则lastMessageTime设置为当前时间
            if (empty($connection->lastMessageTime)) {
                $connection->lastMessageTime = $time_now;
                continue;
            }

            // 上次通讯时间间隔大于心跳间隔，则认为客户端已经下线，关闭连接
            if ($time_now - $connection->lastMessageTime > HEARTBEAT_TIME) {
                echo iconv("utf8", 'gbk',"太久未回应，关闭连接");
                $connection->send(iconv("utf8", 'gbk',"太久未回应，关闭连接"));
                $connection->close();
            }else{
//                echo "心跳：".date("Y-m-d H:i:s", $connection->lastMessageTime).PHP_EOL;
            }
        }
    });
};

// 接收到浏览器发送的数据时回复hello world给浏览器
$http_worker->onMessage = function($connection, $data)
{
    global $http_worker;
    global $db;
    global $redis;

    $connection->lastMessageTime = time();

    $str = bytetostr($data);

    $redis->get("delConnections", function ($r, $redis){
        if(intval(date("H"))<1 && !$r){
            $redis->set("delConnections", 1, 3660);
            $redis->del("Connections");
        }
    });


    // 判断当前客户端是否已经验证,即是否设置了uid
    if(!isset($connection->uid))
    {
        $uid = 'a';
        if(substr($str, 0,4)=="4E42"){
            $uid = hexToStr(substr($str,0,8).substr($str,14,-2));
        }

        $connection->uid = $uid;

        /* 保存uid到connection的映射，这样可以方便的通过uid查找connection，
         * 实现针对特定uid推送数据
         */
        $http_worker->uidConnections[$connection->uid] = $connection;

        $redis->zScore("Connections", $uid, function ($c_num, $redis) use ($uid){
            $c_num = $c_num??0;
            $c_num = $c_num+1;
            $redis->zAdd("Connections", $c_num, $uid);
        });

        if(substr($uid,0,4)=='NBSE' && strlen($uid)==12 && strstr($str,'4E425345100081') && strlen($data)==16){
            $redis->lpush("BoardStart", $uid);

            $redis->set($uid, $connection->id, 20);

            //记录开始日志
            $redis->lpush("ConnTimes_".$uid, "start_".date("Y-m-d H:i:s")."_".$connection->getRemoteIp());

            $folderPath = __DIR__.'/log/'.date("Ym").'/'.date("d");
            is_dir($folderPath) || @mkdir($folderPath) || die("Can't Create folder");

            $file_directory = $folderPath.'/'.$connection->uid.".log";
            $file_directory = strval(str_replace("\0", "", $file_directory));

            file_put_contents($file_directory, "\n连接开始",FILE_APPEND);
        }

    }

    if(substr($str, 0,4)=="4E42" && $connection->uid!='a'){
        $folderPath = __DIR__.'/log/'.date("Ym").'/'.date("d");
        is_dir($folderPath) || @mkdir($folderPath) || die("Can't Create folder");

        $file_directory = $folderPath.'/'.$connection->uid.".log";
        $file_directory = strval(str_replace("\0", "", $file_directory));

        file_put_contents($file_directory, "\n".date("Y-m-d H:i:s").',  ip:'.$connection->getRemoteIp()."\n",FILE_APPEND);

        file_put_contents($file_directory, $connection->id.','.$str.",".(hexToStr($str))."\n",FILE_APPEND);

        if($str == '4E42534508008092'){
            $connection->send(tobyte('4E4253450900800093'));
            if($connection->uid){
                $redis->set($connection->uid, $connection->id, 20);

                /* 保存uid到connection的映射，这样可以方便的通过uid查找connection，
                 * 实现针对特定uid推送数据
                 */
                $http_worker->uidConnections[$connection->uid] = $connection;
            }
        }else if(strstr($str,'4E425345100081')){
            $ban = substr($str,10,2);
            echo "connection_id：".$connection->id."，connection_uid：".$connection->uid.",获取用户设备号：".hexToStr(substr($str,0,8).substr($str,14,-2));
            echo ",版地址：".$ban.",接收命令：".$str.PHP_EOL;

            $device_id = hexToStr(substr($str,0,8).substr($str,14,-2));

            $redis->expire($connection->uid??'a', 20);

            $redis->get("D_ID".$device_id, function ($res) use ($db, $connection){
                if($res){
                    $db->update('jcg_devices')->cols(array('online'=>1))->where('id='.$res)->query();
                }
            });

        }else if(strstr($str,'4E42534522')){
            $strs = substr($str, 18,-2);
            $board = substr($str, 10,2);

            $redis->set("BoardStatus".$connection->uid.$board, 1, 2);

            //执行获取板子所有通道状态命令
            $redis->get("D_ID".$connection->uid, function ($res, $redis) use ($db, $connection, $strs, $board, $str){
                if($res){
                    $info['id'] = $res;
                    if(($info['id']??0)){
                        $chs_a = $chs_b = [];
                        $list = str_split($strs, 2);
                        foreach($list as $key=>$row){
                            if($row=='00'){
                                //门关闭
                                $chs_a[$key+1] = 1;
                            }
                            if($row=='01'){
                                //门打开
                                $chs_b[$key+1] = 0;
                            }
                        }

                        echo PHP_EOL.date("Y-m-d H:i:s").",".$connection->id.','.$connection->uid."版号".$board."的箱门数量：".count($list)."，".$strs.",接收命令：".$str.PHP_EOL;

                        if($chs_a) {
                            //更新门关闭
                            $query = "UPDATE `jcg_boxs` SET `is_lock` = 1 , box_status=IF(user_id>0,2,1) WHERE ".'d_id='.$info['id']." AND device_board='".$board."'".' AND board_channel in ('.implode(',',array_keys($chs_a)).')';
                            $db->query($query);
                        }
                        if($chs_b) {
                            //更新门打开
                            $query = "UPDATE `jcg_boxs` SET `is_lock` = 0 , box_status=IF(user_id>0,2,1) WHERE ".'d_id='.$info['id']." AND device_board='".$board."'".' AND board_channel in ('.implode(',',array_keys($chs_b)).')';
                            $db->query($query);
                        }
                        $db->update('jcg_devices')->cols(array('online'=>1))->where('id='.$res)->query();
                    }
                }
            });

        }else if(strstr($str,'4E425345B')){
            $ch = hexdec(str_replace("0","",substr($str, 15,2)));
            $board = substr($str, 9,2);
            $status = substr($str, 17,-2)=="00"?1:0;

            echo date("Y-m-d H:i:s")."：收到版号".$connection->uid."模板".$board."通道".$ch."开锁回应：".$str.','.$connection->id.PHP_EOL;

            $keys = $connection->uid.'_'.$board.'_'.$ch;
            $redis->set("OpenCmd".$keys, 1, 10);
            $redis->lpush("OpenCmd", $keys."_".date("Y-m-d H:i:s"));

            //开门命令交互
            $redis->get("D_ID".$connection->uid, function ($res, $redis) use ($db, $connection, $ch, $board, $status){
                if($res){
                    $info['id'] = $res;
                    if(($info['id']??0)){
                        $query = "UPDATE `jcg_boxs` SET `is_lock` = ".$status." , box_status=IF(user_id>0,2,1) WHERE ".'d_id='.$info['id'].' AND board_channel='.$ch." AND device_board='{$board}'";

                        $db->query($query);

                        //开门时记录关门提醒
                        $keys = $info['id'].'_'.$board.'_'.$ch;
                        $redis->get($keys, function ($result, $redis) use ($keys) {
                            if($result){
                                $redis->ZADD("OPENBOX", time(), $keys);
                            }
                        });

                        //接收到了开锁的命令 删除 redis 开锁命令
                        $redis->ZREM("UnLockCommand", $keys);

                        //加入开门指令
                        $redis->get("PayBox_".$keys, function ($result, $redis) use ($connection, $keys) {
                            if($result){
                                $key = "LockUnlock:".$result;
                                $value = "unlock_".date("Y-m-d H:i:s")."_".$connection->getRemoteIp();
                                $redis->get("TempLock".$result, function ($res, $redis) use ($key, $value){
                                    if($res ) $value = "temp-".$value;
                                    $redis->lpush($key, $value);
                                });
                            }
                        });

                        $redis->del($connection->uid.$board.$ch);
                    }
                }
            });
        }else if(strstr($str,'4E425345A')){
            $len = stripos($str,"5345A");
            $str = substr($str,$len-4,19);

            //关门命令交互
            $redis->get("D_ID".$connection->uid, function ($res, $redis) use ($db, $connection, $str) {
                if ($res) {
                    $info['id'] = $res;
                    if (($info['id'] ?? 0)) {
                        $ch = hexdec(str_replace("0", "", substr($str, 13, 2)));

                        $board = substr($str, 9, 2);
                        $status = substr($str, 15, -2) == "00" ? 1 : 0;

                        $query = "UPDATE `jcg_boxs` SET `is_lock` = " . $status . " , box_status=IF(user_id>0,2,1) WHERE " . 'd_id=' . $info['id'] . ' AND board_channel=' . $ch . " AND device_board='{$board}'";

                        //关门时删除关门提醒
                        $keys = $info['id'] . '_' . $board . '_' . $ch;
                        $redis->get($keys, function ($result, $redis) use ($keys) {
                            if ($result) {
                                $redis->del($keys);
                                $redis->ZREM("OPENBOX", $keys);
                            }
                        });

                        //加入关门指令
                        $redis->get("PayBox_" . $keys, function ($result, $redis) use ($connection, $keys) {
                            if ($result) {
                                $redis->lpush("LockUnlock:" . $result, "lock_" . date("Y-m-d H:i:s") . "_" . $connection->getRemoteIp());
                            }
                        });

                        //预防意外，关锁 再删除一次 redis 开锁命令
                        $redis->ZREM("UnLockCommand", $keys);

                        $db->query($query);
                    }
                }
            });
        }
    }else{
        $data = json_decode($data, true);
        $data_type = "";
        if(is_array($data)){
            $data_type = $data['type']??"";
        }
        if(!isset($data['is_auto'])){
//            echo "IP:".$connection->getRemoteIp().",".date("Y-m-d H:i:s").',';
            $redis->del("ALL_".$data['device_id']);
        }else if(isset($data['device_id']) && isset($data['is_auto'])){
            $redis->del("ALL_".$data['device_id']);
            $redis->set("ALL_".$data['device_id'], 1, 10);
        }
        if($data_type==1){
            if(isset($data['ch']) && $data['ch']){
                $chs = "4E42534509".$data['device_board']."830".strtoupper(dechex($data['ch']));
                $chs .= getXOR($chs);
                echo "发送版号".$data['device_id']."模板".$data['device_board']."获取通道".$data['ch']."锁状态：".$chs.PHP_EOL;
                sendMessageByUid($data['device_id'], tobyte($chs));
            }else{
                if(!isset($data['is_auto'])){
                    echo "发送版号".$data['device_id']."模板".$data['device_board']."获取所有通道锁状态：";
                }
                $cmd = '4E42534508'.$data['device_board'].'84';
                $cmd .= getXOR($cmd);
                sendMessageByUid($data['device_id'], tobyte($cmd));
            }
        }else if($data_type==2){
            if(isset($data['ch']) && $data['ch']){
                $unlock = '4E42534509'.$data['device_board'].'820'.strtoupper(dechex($data['ch']));
                $unlock .= getXOR($unlock);
                echo date("Y-m-d H:i:s")."：发送版号".$data['device_id']."模板".$data['device_board']."通道".$data['ch']."开锁指令：".$unlock;
                sendMessageByUid($data['device_id'], tobyte($unlock)).PHP_EOL;
            }else{
                $unlock = '4E42534508'.$data['device_board'].'86';
                $unlock .= getXOR($unlock);
                echo "发送版号".$data['device_id'].$data['device_board']."所有通道开锁指令：".$unlock.PHP_EOL;
                sendMessageByUid($data['device_id'], tobyte($unlock)).PHP_EOL;
            }
        }else if($data_type==3){
            echo "发送版号".$data['device_id']."获取设备号指令".PHP_EOL;
            sendMessageByUid($data['device_id'], tobyte('4E42534508'.$data['device_board'].'8193')).PHP_EOL;
        }else if($data_type==4){
            echo "发送版号".$data['device_id']."开全部通道锁".PHP_EOL;
            $redis->get("D_ID".$connection->uid, function ($res) use ($db, $connection, $data){
                if($res){
                    $info['id'] = $res;
                    if(($info['id']??0)){
                        $sql = "SELECT device_board FROM `jcg_boxs` WHERE d_id=".$info['id']." GROUP BY device_board";
                        $list = $db->query($sql);
                        if($list && count($list)>=1){
                            //发送获取所有通道锁状态
                            foreach($list as $row){
                                $ml = '4E42534508'.$row['device_board'].'86';
                                $ml .= getXOR($ml);
                                sendMessageByUid($data['device_id'], tobyte($ml));
                            }
                        }
                    }
                }
            });
        }else{
            $folderPath = __DIR__.'/log/'.date("Ym");
            is_dir($folderPath) || @mkdir($folderPath) || die("Can't Create folder");

            $file_directory = $folderPath.'/'.date("d").".log";
            file_put_contents($file_directory, $data."\n",FILE_APPEND);
        }
    }
};

function sendMessageByUid($uid, $message)
{
    global $http_worker;
    global $redis;
    global $db;
    if(isset($http_worker->uidConnections[$uid]))
    {
        $connection = $http_worker->uidConnections[$uid];
        $res = $connection->send($message);

        echo ",".$connection->id.PHP_EOL;

        if(!$res && substr($uid,0,4)=='NBSE'){
            $redis->get("D_ID".$connection->uid, function ($res) use ($db, $connection, $uid) {
                if ($res) {
                    $info['id'] = $res;
                    if (($info['id'] ?? 0)) {
                        $db->update('jcg_devices')->cols(array('online' => 0))->where("device_id='" . $uid . "'")->query();
                        $db->update('jcg_boxs')->cols(array('box_status' => 3, 'is_lock' => 2))->where("d_id=" . $info['id'])->query();
                    }
                }
            });

            $redis->del($uid);

            $folderPath = __DIR__.'/log/'.date("Ym").'/'.date("d");
            is_dir($folderPath) || @mkdir($folderPath) || die("Can't Create folder");

            $file_directory = $folderPath.'/'.$connection->uid.".log";

            file_put_contents($file_directory, "\n结束：".date("Y-m-d H:i:s").',  ip:'.$connection->getRemoteIp()."\n",FILE_APPEND);

            //记录结束日志
            $redis->lpush("ConnTimes_".$uid, "end_".date("Y-m-d H:i:s")."_".$connection->getRemoteIp());
        }
    }else{
        if(substr($uid,0,4)=='NBSE'){
            $redis->get("D_ID".$uid, function ($res) use ($db, $uid) {
                if ($res) {
                    $info['id'] = $res;
                    if (($info['id'] ?? 0)) {
                        $db->update('jcg_devices')->cols(array('online'=>0))->where("device_id='".$uid."'")->query();
                        $db->update('jcg_boxs')->cols(array('box_status'=>3, 'is_lock'=>2))->where("d_id=".$info['id'])->query();
                    }
                }
            });
            $redis->del($uid);

            $folderPath = __DIR__.'/log/'.date("Ym").'/'.date("d");
            is_dir($folderPath) || @mkdir($folderPath) || die("Can't Create folder");

            $file_directory = $folderPath.'/'.$uid.".log";

            file_put_contents($file_directory, "\n结束：".date("Y-m-d H:i:s")."\n",FILE_APPEND);

            //记录结束日志
            $redis->lpush("ConnTimes_".$uid, "end_".date("Y-m-d H:i:s")."_");
        }
    }
}

$http_worker->onClose = function($connection)
{
    global $db;
    global $redis;
    if(isset($connection->uid)){
        if($connection->getRemoteIp()!='127.0.0.1'){
            echo "id:".$connection->id.",IP:".$connection->getRemoteIp().','.$connection->uid." connection closed\n";
        }
        if(substr($connection->uid,0,4)=='NBSE') {
            $redis->get($connection->uid, function($result, $redis) use($db, $connection) {
                if($result==$connection->id || empty($result)){
                    $redis->get("D_ID".$connection->uid, function ($res) use ($db, $connection) {
                        if ($res) {
                            $info['id'] = $res;
                            if (($info['id'] ?? 0)) {
                                $db->update('jcg_devices')->cols(array('online'=>0))->where("device_id='".$connection->uid."'")->query();
                                $db->update('jcg_boxs')->cols(array('box_status'=>3, 'is_lock'=>2))->where("d_id=".$info['id'])->query();
                            }
                        }
                    });
                    $redis->del($connection->uid);
                }
            });

            $folderPath = __DIR__.'/log/'.date("Ym").'/'.date("d");
            is_dir($folderPath) || @mkdir($folderPath) || die("Can't Create folder");

            $file_directory = $folderPath.'/'.$connection->uid.".log";
            $file_directory = strval(str_replace("\0", "", $file_directory));

            file_put_contents($file_directory, "\n结束：".date("Y-m-d H:i:s").',  ip:'.$connection->getRemoteIp()."\n",FILE_APPEND);

            //记录结束日志
            $redis->lpush("ConnTimes_".$connection->uid, "end_".date("Y-m-d H:i:s")."_".$connection->getRemoteIp());
        }
    }else{
        if($connection->getRemoteIp()!='127.0.0.1'){
            echo "IP:".$connection->getRemoteIp().','.$connection->id." connection closed\n";
        }
    }
};


// 运行worker
Worker::runAll();